package com.sheng.project.study.cglib;

import org.junit.Test;
import org.springframework.cglib.proxy.*;

import java.lang.reflect.Method;

/**
 * CGLIB和Java动态代理的区别
 * Java动态代理只能够对接口进行代理，不能对普通的类进行代理（因为所有生成的代理类的父类为Proxy，Java类继承机制不允许多重继承）；CGLIB能够代理普通类；
 * Java动态代理使用Java原生的反射API进行操作，在生成类上比较高效；CGLIB使用ASM框架直接对字节码进行操作，在类的执行过程中比较高效
 *
 * @author liusheng
 * @date 2023/06/29
 */
public class CglibTest {

    @Test
    public void test1() {
        // 使用 Enhancer 来给某个类创建代理类，步骤
        // 1.创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        // 2.通过 setSuperclass 来设置父类型，即需要给哪个类创建代理类
        enhancer.setSuperclass(Service1.class);
        // 3.设置回调，需实现 org.springframework.cglib.proxy.Callback 接口
        /*
          3.设置回调，需实现 org.springframework.cglib.proxy.Callback 接口
          此处使用 org.springframeword.cglib.proxy.MethodInterceptor，也是一个接口，实现了 Callback 接口
          当调用代理对象的任何方法的时候，都会被 MethodInterceptor 接口的 invoke 方法处理
         */
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 代理对象方法拦截器
             * @param o 代理对象
             * @param method 被代理的类的方法，即Service1中的方法
             * @param objects 调用方法传递的参数
             * @param methodProxy 方法代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("调用方法：" + method);
                // 可以调用 MethodProxy 的 invokeSuper 调用被代理类的方法
                Object result = methodProxy.invokeSuper(o, objects);
                return result;
            }
        });
        // 4.获取代理对象，调用 enhancer.create 方法获取代理对象，这个方法返回的是 Object 类型，所以需要强转一下
        Service1 service1 = (Service1) enhancer.create();
        // 5.调用代理对象的方法
        service1.m1();
        service1.m2();
    }

    @Test
    public void test2() {
        // spring 中的 @configuration 注解就是采用这种方式实现的
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service2.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("调用方法:" + method);
                Object result = methodProxy.invokeSuper(o, objects);
                return result;
            }
        });
        Service2 proxy = (Service2) enhancer.create();
        proxy.m1(); //@1
    }

    @Test
    public void test3() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service3.class);
        // 当调用某个类的任何方法的时候，都希望返回一个固定的值，此时可以使用 FixedValue 接口
        enhancer.setCallback(new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return "路人甲";
            }
        });
        Service3 proxy = (Service3) enhancer.create();
        System.out.println(proxy.m1());//@1
        System.out.println(proxy.m2()); //@2
        System.out.println(proxy);//@3
    }

    @Test
    public void test4() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service4.class);
        // 不可行
//        enhancer.setCallback(new MethodInterceptor() {
//            @Override
//            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//                String name = method.getName();
//                if (name.indexOf("insert") == 0) {
//                    // 统计耗时
//                    long start = System.nanoTime();
//                    Object result = methodProxy.invokeSuper(o, objects);
//                    long end = System.nanoTime();
//                    System.out.println("方法 " + method.getDeclaringClass().getName() + "." + method.getName() + "() 耗时：" + (end - start));
//                    return result;
//                } else if (name.indexOf("get") == 0) {
//                    // 直接返回固定字符串
//                    return "返回固定字符串";
//                }
//                return null;
//            }
//        });
        // 由于需求中要对不同的方法做不同的处理，所以需要有2个Callback对象，
        // 当调用代理对象的方法的时候，具体会走哪个Callback呢，
        // 此时会通过CallbackFilter中的accept来判断，这个方法返回callbacks数组的索引。
        Callback[] callbacks = {
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        // 统计耗时
                        long start = System.nanoTime();
                        Object result = methodProxy.invokeSuper(o, objects);
                        long end = System.nanoTime();
                        System.out.println("方法 " + method.getDeclaringClass().getName() + "." + method.getName() + "() 耗时：" + (end - start));
                        return result;
                    }
                },
                new FixedValue() {
                    @Override
                    public Object loadObject() throws Exception {
                        return "固定字符串";
                    }
                }
        };
        enhancer.setCallbacks(callbacks);
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {
                String name = method.getName();
                return name.startsWith("insert") ? 0 : 1;
            }
        });
        Service4 proxy = (Service4) enhancer.create();
        proxy.insert1();
        proxy.insert2();
        System.out.println(proxy.get1());
        System.out.println(proxy.get2());
    }

    @Test
    public void test5() {
        // 对 test4 进行优化
        //创建2个Callback
        Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
            long start = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            long end = System.nanoTime();
            System.out.println("方法 " + method.getDeclaringClass().getName() + "." + method.getName() + "() 耗时：" + (end - start));
            return result;
        };
        //下面这个用来拦截所有get开头的方法，返回固定值的
        Callback fixdValueCallback = (FixedValue) () -> "固定字符串";
        CallbackHelper callbackHelper = new CallbackHelper(Service4.class, null) {
            @Override
            protected Object getCallback(Method method) {
                return method.getName().startsWith("insert") ? costTimeCallback : fixdValueCallback;
            }
        };
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service4.class);
        enhancer.setCallbacks(callbackHelper.getCallbacks());
        enhancer.setCallbackFilter(callbackHelper);
        Service4 proxy = (Service4) enhancer.create();
        proxy.insert1();
        proxy.insert2();
        System.out.println(proxy.get1());
        System.out.println(proxy.get2());
    }

    @Test
    public void test6() {
        // Callback 接口下面有个子接口 org.springframework.cglib.proxy.NoOp，将这个作为 Callback 的时候，被调用的方法会直接放行，像没有任何代理一样
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service3.class);
        enhancer.setCallback(NoOp.INSTANCE);
        Service3 proxy = (Service3) enhancer.create();
        System.out.println(proxy.m1());
        System.out.println(proxy.m2());
    }

    @Test
    public void test7() {
        // 测试使用通用代理类
        // 创建 Service1 代理
        Service1 service1 = CostTimeProxy.createProxy(new Service1());
        service1.m1();
        // 创建 Service3 代理
        Service3 service3 = CostTimeProxy.createProxy(new Service3());
        System.out.println(service3.m1());
    }
}
